<template>
  <div style="width: 100%; height: 800px">
    <!--<meta name="twitter:image" content="https://aatishb.com/covidtrends/logos/covid-trends-logo-nowords.png">-->
    <!--&lt;!&ndash; Open Graph data &ndash;&gt;-->
    <!--<meta property="og:url" content="https://aatishb.com/covidtrends/" />-->
    <!--<meta property="og:image" content="https://aatishb.com/covidtrends/logos/covid-trends-logo-nowords.png" />-->
    <!--<meta property="og:description" content="Visualizing the exponential growth of COVID-19 across the world." />-->
    <!--<meta property="og:description" content="Visualizing the exponential growth of COVID-19 across the world." />-->

    <div id="root">
      <!--<header>-->
      <!--    <div class="explainer">-->
      <!--      <p>This interactive charts the <b><i>new</i></b> {{selectedData.toLowerCase()}} of COVID-19 in the past week vs. the <b><i>total</i></b> {{selectedData.toLowerCase()}} to date. When plotted in this way, exponential growth is represented as a straight line that slopes upwards. Notice that almost all countries follow a very similar path of exponential growth. <i>We're all in this together.</i> <span v-if="isHidden"><a @click="toggleHide">Learn more.</a></span></p>-->

      <!--      <span v-if="!isHidden">-->

      <!--        <p>To learn more about this graph, watch this video created by Minute Physics.</p>-->
      <!--        <br>-->

      <!--        <iframe width="560" height="315" src="https://www.youtube.com/embed/54XLXg4fYsc" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>-->

      <!--        <p><i><b>Note:</b> The x-axis of this graph is not time</i>, but is instead the total number of cases or deaths.</p>-->

      <!--        <p><b>Tips:</b> Press Space (or the play button) to Play/Pause. Press +/- keys (or drag the slider) to see daily changes. Hover over the graph for more info. Drag your mouse to zoom in, doubleclick to zoom back out. And don't forget to wash your hands!</p>-->

      <!--        <a id="hide" @click="toggleHide">Hide</a>-->
      <!--      </span>-->
      <!--    </div>-->

      <!--    <div class="logowrapper">-->

      <!--      <h1><a href="https://aatishb.com/covidtrends">Covid Trends</a></h1>-->

      <!--      <div v-if="!isHidden" class="logos">-->
      <!--        <a href="https://aatishb.com/" aria-label="Visit the website of Aatish Bhatia"><img src="logos/aatishb-bw.png" height="40" alt="Avatar of Aatish Bhatia"></a>-->
      <!--        <a href="https://www.youtube.com/user/minutephysics" aria-label="Visit the Youtube channel of Minute Physics"><img src="logos/minutephysics-invert.png" height="40" alt="Minute Physics Logo"></a>-->
      <!--      </div>-->

      <!--    </div>-->

      <!--</header>-->

      <div class="ctapp">

        <main id="main">

          <!--核心图-->
          <graph v-if="covidData.length > 0 && countries.some(country => selectedCountries.includes(country))" :graph-data="graphData" :day="day" :resize="isHidden" v-bind.sync="graphAttributes"></graph>

          <div v-if="covidData.length > 0 && countries.some(country => selectedCountries.includes(country))" class="nav">

            <div class="navelement">
              <img v-show="paused" @click="play" src="./icons/play.svg" alt="Play" style="width: 3rem;">
              <img v-show="!paused" @click="play" src="./icons/pause.svg" alt="Pause" style="width: 3rem;">
            </div>

            <div class="navelement">
              <time class="h2" :datetime="formatDate(minDay > 0 ? dates[day - 1] : dates[dates.length - 1])">{{ formatDate(minDay > 0 ? dates[day - 1] : dates[dates.length - 1]) }}</time>
            </div>

            <div class="navelement slidercontainer">
              <input v-if="dates.length > 7" type="range" :min="minDay > 0 ? minDay : dates.length" :max="dates.length" step="1" v-model="day" class="slider" @mousedown="pause" aria-label="Choose the current observation day">
            </div>

            <div class="navelement">
              <select @change="createURL" v-model="selectedScale" @mousedown="pause" aria-Label="Select Scale">
                <option v-for="s in scale" v-bind:value="s" v-bind:key="s">
                  {{ s }}
                </option>
              </select>
            </div>

          </div>

          <div v-if="!firstLoad && covidData.length == 0" class="nodata"><span>Not enough data for these parameters.</span></div>
          <div v-if="!firstLoad && covidData.length != 0 && !countries.some(country => selectedCountries.includes(country))" class="nodata"><span>Please select some {{regionType.toLowerCase()}} to display.</span></div>

          <!--<footer>-->
          <!--  Created by <a href="https://aatishb.com/">Aatish Bhatia</a> in collaboration with <a href="https://www.youtube.com/user/minutephysics">Minute Physics</a> &middot; World data provided by <a href="https://github.com/CSSEGISandData/COVID-19">Johns Hopkins University</a> (updated daily around 00:00 UTC / 20:00 ET) &middot; US state data provided by <a href="https://github.com/nytimes/covid-19-data">NYTimes</a> (updated daily around 16:00 UTC / 12:00 ET)&middot; Shortcuts: +/- for daily changes, space to play/pause &middot; <a href="https://github.com/aatishb/covidtrends#credits">Credits & Source</a> &middot; <a href="https://www.cdc.gov/coronavirus/2019-ncov/prepare/prevention.html">Stay safe!</a>-->
          <!--</footer>-->

        </main>

        <aside>

          <div>
            <h2>Customize</h2>
            <label for="selectedData">Select Parameter</label>
            <select id="selectedData" v-model="selectedData" @mousedown="pause">
              <option v-for="d in dataTypes" v-bind:value="d" v-bind:key="d">
                {{ d }}
              </option>
            </select>
            <label for="selectedRegion">Select Region</label>
            <select id="selectedRegion" v-model="selectedRegion" @mousedown="pause">
              <option v-for="s in regions" v-bind:value="s" v-bind:key="s">
                {{ s }}
              </option>
            </select>
            <label for="selectedScale">Select Scale</label>
            <select @change="createURL" id="selectedScale" v-model="selectedScale" @mousedown="pause">
              <option v-for="s in scale" v-bind:value="s" v-bind:key="s">
                {{ s }}
              </option>
            </select>
            <input type="checkbox" style="margin-top: 0.75rem;" id="showLabels" v-model="showLabels">
            <label for="showLabels">Show Labels</label>

            <div>
              <input @change="createURL" type="checkbox" style="margin-top: 0.75rem;" id="showTrendLine" v-model="showTrendLine">
              <label for="showTrendLine">Show
                <input @change="createURL" type="number" min="1" step="1" style="width: 3rem; padding: 0rem; font-size: inherit;" v-model.number="doublingTime" @focus="pause"  aria-label="Doubling Time in Days">
                 Day Doubling Time</label>
            </div>

          </div>

          <div class="countries">

            <h2>{{regionType}}</h2>

            <div class="search">
              <input id="searchField" v-model="searchField" placeholder="Search For A Location" aria-label="Search For A Location">
            </div>

            <div class="buttonwrapper">
              <button @click="deselectAll" aria-Label="Deselect All Regions">Deselect All</button>
              <button @click="selectAll" aria-Label="Select All Regions">Select All</button>
            </div>

            <p style="padding-top: 1rem;">Showing {{regionType}} with at least {{minCasesInCountry}} {{selectedData}}</p>

            <ul>
              <li v-for="country in visibleCountries" v-bind:key="country">
                <input @change="createURL" type="checkbox" :id="country" :value="country" v-model="selectedCountries">
                <label :for="country">{{country}}</label>
              </li>
            </ul>

          </div>

        </aside>

      </div>

    </div>
  </div>
</template>

<script>
import Graph from './Graph';
import './plotly-basic.min'

export default {
  name: 'Trend',
  components: {
    Graph
  },
  data() {
    return {
      paused: true,

      dataTypes: ['Confirmed Cases', 'Reported Deaths'],

      selectedData: 'Confirmed Cases',

      regions: ['World', 'US', 'China', 'Australia', 'Canada'],

      selectedRegion: 'World',

      sliderSelected: false,

      day: 7,

      lookbackTime: 7,

      scale: ['Logarithmic Scale', 'Linear Scale'],

      selectedScale: 'Logarithmic Scale',

      minCasesInCountry: 50,

      dates: [],

      covidData: [],

      countries: [],

      visibleCountries: [], // used for search

      selectedCountries: [], // used to manually select countries

      defaultCountries: [], // used for createURL default check

      isHidden: true,

      showLabels: true,

      showTrendLine: true,

      doublingTime: 2,

      mySelect: '',

      searchField: '',

      autoplay: true,

      firstLoad: true,

      graphAttributes: {
        mounted: false,
        innerWidth: NaN,
        innerHeight: NaN,
        width: NaN,
        height: NaN,
        referenceLineAngle: NaN
      }
    };
  },

  mounted() {
    this.pullData(this.selectedData, this.selectedRegion);
  },

  created() {

    let url = window.location.href.split('?');

    if (url.length > 1) {

      let urlParameters = new URLSearchParams(url[1]);

      if (urlParameters.has('scale')) {

        let myScale = urlParameters.get('scale').toLowerCase();

        if (myScale === 'log') {
          this.selectedScale = 'Logarithmic Scale';
        } else if (myScale === 'linear') {
          this.selectedScale = 'Linear Scale';
        }
      }

      if (urlParameters.has('data')) {
        let myData = urlParameters.get('data').toLowerCase();
        if (myData === 'cases') {
          this.selectedData = 'Confirmed Cases';
        } else if (myData === 'deaths') {
          this.selectedData = 'Reported Deaths';
        }

      }

      if (urlParameters.has('region')) {
        let myRegion = urlParameters.get('region');
        if (this.regions.includes(myRegion)) {
          this.selectedRegion = myRegion;
        }
      }

      // since this rename came later, use the old name to not break existing URLs
      let renames = {
        'China': 'China (Mainland)'
      };

      // before we added regions, the url parameter was called country instead of location
      // we still check for this so as to not break existing URLs
      if (urlParameters.has('country')) {
        this.selectedCountries = urlParameters.getAll('country').map(e => Object.keys(renames).includes(e) ? renames[e] : e);
      } else if (urlParameters.has('location')) {
        this.selectedCountries = urlParameters.getAll('location').map(e => Object.keys(renames).includes(e) ? renames[e] : e);
      }

      if (urlParameters.has('trendline')) {
        let showTrendLine = urlParameters.get('trendline');
        this.showTrendLine = (showTrendLine === 'true');
      } else if (urlParameters.has('doublingtime')) {
        let doublingTime = urlParameters.get('doublingtime');
        this.doublingTime = doublingTime;
      }

      if (urlParameters.has('select')) {
        this.mySelect = urlParameters.get('select').toLowerCase();
      }

    }

    window.addEventListener('keydown', e => {

      if ((e.key === ' ') && this.dates.length > 0) {
        this.play();
      }

      else if ((e.key === '-' || e.key === '_') && this.dates.length > 0) {
        this.paused = true;
        this.day = Math.max(this.day - 1, this.minDay);
      }

      else if ((e.key === '+' || e.key === '=') && this.dates.length > 0) {
        this.paused = true;
        this.day = Math.min(this.day + 1, this.dates.length);
      }

    });

  },

  watch: {
    selectedData() {
      if (!this.firstLoad) {
        this.pullData(this.selectedData, this.selectedRegion, /*updateSelectedCountries*/ false);
      }
      this.searchField = '';
    },

    selectedRegion() {
      if (!this.firstLoad) {
        this.pullData(this.selectedData, this.selectedRegion, /*updateSelectedCountries*/ true);
      }
      this.searchField = '';
    },

    minDay() {
      if (this.day < this.minDay) {
        this.day = this.minDay;
      }
    },

    'graphAttributes.mounted': function() {

      if (this.graphAttributes.mounted && this.autoplay && this.minDay > 0) {
        this.day = this.minDay;
        this.play();
        this.autoplay = false; // disable autoplay on first play
      }
    },

    searchField() {
      let debouncedSearch = this.debounce(this.search, 250, false);
      debouncedSearch();
    }
  },

  methods: {

    debounce(func, wait, immediate) { // https://davidwalsh.name/javascript-debounce-function
      var timeout;
      return function() {
        var context = this, args = arguments;
        var later = function() {
          timeout = null;
          if (!immediate) func.apply(context, args);
        };
        var callNow = immediate && !timeout;
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
        if (callNow) func.apply(context, args);
      };
    },

    myMax() { // https://stackoverflow.com/a/12957522
      var par = [];
      for (var i = 0; i < arguments.length; i++) {
        if (!isNaN(arguments[i])) {
          par.push(arguments[i]);
        }
      }
      return Math.max.apply(Math, par);
    },

    myMin() {
      var par = [];
      for (var i = 0; i < arguments.length; i++) {
        if (!isNaN(arguments[i])) {
          par.push(arguments[i]);
        }
      }
      return Math.min.apply(Math, par);
    },

    pullData(selectedData, selectedRegion, updateSelectedCountries = true) {

      if (selectedRegion !== 'US') {
        let url;
        if (selectedData === 'Confirmed Cases') {
          url = 'https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_confirmed_global.csv';
        } else if (selectedData === 'Reported Deaths') {
          url = 'https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_deaths_global.csv';
        } else {
          return;
        }
        Plotly.d3.csv(url, (data) => this.processData(data, selectedRegion, updateSelectedCountries));
      } else { // selectedRegion === 'US'
        const type = (selectedData === 'Reported Deaths') ? 'deaths' : 'cases';
        const url = 'https://raw.githubusercontent.com/nytimes/covid-19-data/master/us-states.csv';
        Plotly.d3.csv(url, (data) => this.processData(this.preprocessNYTData(data, type), selectedRegion, updateSelectedCountries));
      }
    },

    removeRepeats(array) {
      return [...new Set(array)];
    },

    groupByCountry(data, dates, regionsToPullToCountryLevel /* pulls out Hong Kong & Macau from region to country level */) {

      let countries = data.map(e => e['Country/Region']);
      countries = this.removeRepeats(countries);

      let grouped = [];
      for (let country of countries) {

        // filter data for this country (& exclude regions we're pulling to country level)
        // e.g. Mainland China numbers should not include Hong Kong & Macau, to avoid double counting
        let countryData = data.filter(e => e['Country/Region'] === country)
          .filter(e => !regionsToPullToCountryLevel.includes(e['Province/State']));

        const row = {region: country};

        for (let date of dates) {
          let sum = countryData.map(e => parseInt(e[date]) || 0).reduce((a, b) => a + b);
          row[date] = sum;
        }

        grouped.push(row);

      }

      return grouped;
    },

    filterByCountry(data, dates, selectedRegion) {
      return data.filter(e => e['Country/Region'] === selectedRegion)
        .map(e => Object.assign({}, e, {region: e['Province/State']}));
    },

    convertStateToCountry(data, dates, selectedRegion) {
      return data.filter(e => e['Province/State'] === selectedRegion)
        .map(e => Object.assign({}, e, {region: e['Province/State']}));
    },

    processData(data, selectedRegion, updateSelectedCountries) {
      let dates = Object.keys(data[0]).slice(4);
      this.dates = dates;
      this.day = this.dates.length;

      let regionsToPullToCountryLevel = ['Hong Kong', 'Macau'];

      let grouped;

      if (selectedRegion === 'World') {
        grouped = this.groupByCountry(data, dates, regionsToPullToCountryLevel);

        // pull Hong Kong and Macau to Country level
        for (let region of regionsToPullToCountryLevel) {
          let country = this.convertStateToCountry(data, dates, region);
          if (country.length === 1) {
            grouped = grouped.concat(country);
          }
        }

      } else {
        grouped = this.filterByCountry(data, dates, selectedRegion)
          .filter(e => !regionsToPullToCountryLevel.includes(e.region)); // also filter our Hong Kong and Macau as subregions of Mainland China
      }

      let exclusions = ['Cruise Ship', 'Diamond Princess'];

      let renames = {
        'Taiwan*': 'Taiwan',
        'Korea, South': 'South Korea',
        'China': 'China (Mainland)'
      };

      let covidData = [];
      for (let row of grouped) {

        if (!exclusions.includes(row.region)) {
          const arr = [];
          for (let date of dates) {
            arr.push(row[date]);
          }
          let slope = arr.map((e, i, a) => e - a[i - this.lookbackTime]);
          let region = row.region;

          if (Object.keys(renames).includes(region)) {
            region = renames[region];
          }

          const cases = arr.map(e => e >= this.minCasesInCountry ? e : NaN);
          covidData.push({
            country: region,
            cases,
            slope: slope.map((e, i) => arr[i] >= this.minCasesInCountry ? e : NaN),
            maxCases: this.myMax(...cases)
          });

        }
      }

      this.covidData = covidData.filter(e => e.maxCases > this.minCasesInCountry);
      this.countries = this.covidData.map(e => e.country).sort();
      this.visibleCountries = this.countries;
      const topCountries = this.covidData.sort((a, b) => b.maxCases - a.maxCases).slice(0, 9).map(e => e.country);
      const notableCountries = ['China (Mainland)', 'India', 'US', // Top 3 by population
        'South Korea', 'Japan', 'Taiwan', 'Singapore', // Observed success so far
        'Hong Kong',            // Was previously included in China's numbers
        'Canada', 'Australia']; // These appear in the region selector

      // TODO: clean this logic up later
      // expected behavior: generate/overwrite selected locations if: 1. data loaded from URL, but no selected locations are loaded. 2. data refreshed (e.g. changing region)
      // but do not overwrite selected locations if 1. selected locations loaded from URL. 2. We switch between confirmed cases <-> deaths
      if ((this.selectedCountries.length === 0 || !this.firstLoad) && updateSelectedCountries) {
        this.selectedCountries = this.countries.filter(e => topCountries.includes(e) || notableCountries.includes(e));

        this.defaultCountries = this.selectedCountries; // Used for createURL default check

        if (this.mySelect === 'all') {
          this.selectedCountries = this.countries;
        } else if (this.mySelect === 'none') {
          this.selectedCountries = [];
        }
        this.mySelect = '';

      }

      this.firstLoad = false;
      this.createURL();
    },

    preprocessNYTData(data, type) {
      let recastData = {};
      data.forEach(e => {
        let st = recastData[e.state] = (recastData[e.state] || {'Province/State': e.state, 'Country/Region': 'US', 'Lat': null, 'Long': null});
        st[fixNYTDate(e.date)] = parseInt(e[type]);
      });
      return Object.values(recastData);

      function fixNYTDate(date) {
        let tmp = date.split('-');
        return `${tmp[1]}/${tmp[2]}/${tmp[0].substr(2)}`;
      }
    },

    formatDate(date) {
      if (!date) {
        return '';
      }

      let [m, d, y] = date.split('/');
      return new Date(Date.UTC(2000 + (+y), m - 1, d)).toISOString().slice(0, 10);
    },

    dateToText(date) {
      if (!date) {
        return '';
      }

      const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

      let [m, d] = date.split('/');
      return monthNames[m - 1] + ' ' + d;
    },

    // TODO: clean up play/pause logic
    play() {
      if (this.paused) {

        if (this.day === this.dates.length) {
          this.day = this.minDay;
        }

        this.paused = false;
        setTimeout(this.increment, 200);

      } else {
        this.paused = true;
      }

    },

    pause() {
      if (!this.paused) {
        this.paused = true;
      }
    },

    increment() {

      if (this.day === this.dates.length || this.minDay < 0) {
        this.day = this.dates.length;
        this.paused = true;
      }
      else if (this.day < this.dates.length) {
        if (!this.paused) {
          this.day++;
          setTimeout(this.increment, 200);
        }
      }

    },

    search() {
      this.visibleCountries = this.countries.filter(e => e.toLowerCase().includes(this.searchField.toLowerCase()));
    },

    selectAll() {
      this.selectedCountries = this.countries;
      this.createURL();
    },

    deselectAll() {
      this.selectedCountries = [];
      this.createURL();
    },

    toggleHide() {
      this.isHidden = !this.isHidden;
    },

    createURL() {

      let queryUrl = new URLSearchParams();

      if (this.selectedScale === 'Linear Scale') {
        queryUrl.append('scale', 'linear');
      }

      if (this.selectedData === 'Reported Deaths') {
        queryUrl.append('data', 'deaths');
      }

      if (this.selectedRegion !== 'World') {
        queryUrl.append('region', this.selectedRegion);
      }

      // since this rename came later, use the old name for URLs to avoid breaking existing URLs
      let renames = {'China (Mainland)': 'China'};

      if (!this.showTrendLine) {
        queryUrl.append('trendline', this.showTrendLine);
      }

      else if (this.doublingTime !== 2) {
        queryUrl.append('doublingtime', this.doublingTime);
      }

      // check if no countries selected
      // edge case: since selectedCountries may be larger than the country list (e.g. when switching from Confirmed Cases to Deaths), we can't simply check if selectedCountries is empty
      // so instead we check if the countries list does not include any of the selected countries
      if (!this.countries.some(country => this.selectedCountries.includes(country))) {
        queryUrl.append('select', 'none');
      }

      // check if all countries selected
      // edge case: since selectedCountries may be larger than the country list (e.g. when switching from Confirmed Cases to Deaths), we can't simply compare array contents
      // so instead we check if the countries list is a proper subset of selectedCountries
      else if (this.countries.every(country => this.selectedCountries.includes(country))) {
        queryUrl.append('select', 'all');
      }

      // else check if selection is different from default countries
      else if (JSON.stringify(this.selectedCountries.sort()) !== JSON.stringify(this.defaultCountries)) {

        // only append to URL the selected countries that are also in the currently displayed country list
        // this is done because of the edge case where selectedCountries may be larger than the country list (e.g. when switching from Confirmed Cases to Deaths)
        let countriesToAppendToUrl = this.selectedCountries.filter(e => this.countries.includes(e));

        // apply renames and append to queryUrl
        countriesToAppendToUrl = countriesToAppendToUrl.map(country => Object.keys(renames).includes(country) ? renames[country] : country);
        countriesToAppendToUrl.forEach(country => queryUrl.append('location', country));
      }

      if (queryUrl.toString() === '') {
        window.history.replaceState({}, 'Covid Trends', location.pathname);
      } else {
        window.history.replaceState({}, 'Covid Trends', '?' + queryUrl.toString());
      }

    },

    // reference line for exponential growth with a given doubling time
    referenceLine(x) {
      return x * (1 - Math.pow(2, -this.lookbackTime / this.doublingTime));
    }

  },

  computed: {

    filteredCovidData() {
      return this.covidData.filter(e => this.selectedCountries.includes(e.country));
    },

    minDay() {
      let minDay = this.myMin(...(this.filteredCovidData.map(e => e.slope.findIndex(f => f > 0)).filter(x => x !== -1)));
      if (isFinite(minDay) && !isNaN(minDay)) {
        return minDay + 1;
      } else {
        return -1;
      }
    },

    regionType() {
      switch (this.selectedRegion) {
        case 'World':
          return 'Countries';
        case 'Australia':
        case 'US':
          return 'States / Territories';
        case 'China':
          return 'Provinces';
        case 'Canada':
          return 'Provinces';
        default:
          return 'Regions';
      }
    },

    annotations() {

      return [{
        visible: this.showTrendLine && this.doublingTime > 0,
        x: this.xAnnotation,
        y: this.yAnnotation,
        xref: 'x',
        yref: 'y',
        xshift: -50 * Math.cos(this.graphAttributes.referenceLineAngle),
        yshift: 50 * Math.sin(this.graphAttributes.referenceLineAngle),
        text: this.doublingTime + ' Day Doubling Time<br>of ' + this.selectedData,
        align: 'right',
        showarrow: false,
        textangle: this.graphAttributes.referenceLineAngle * 180 / Math.PI,
        font: {
          family: 'Open Sans, sans-serif',
          color: 'black',
          size: 14
        },
      }];

    },

    layout() {
      return {
        title: 'Trajectory of ' + this.selectedRegion + ' COVID-19 ' + this.selectedData + ' (' + this.formatDate(this.dates[this.day - 1]) + ')',
        showlegend: false,
        autorange: false,
        xaxis: {
          title: 'Total ' + this.selectedData,
          type: this.selectedScale === 'Logarithmic Scale' ? 'log' : 'linear',
          range: this.selectedScale === 'Logarithmic Scale' ? this.logxrange : this.linearxrange,
          titlefont: {
            size: 24,
            color: 'rgba(254, 52, 110,1)'
          },
        },
        yaxis: {
          title: 'New ' + this.selectedData + ' (in the Past Week)',
          type: this.selectedScale === 'Logarithmic Scale' ? 'log' : 'linear',
          range: this.selectedScale === 'Logarithmic Scale' ? this.logyrange : this.linearyrange,
          titlefont: {
            size: 24,
            color: 'rgba(254, 52, 110,1)'
          },
        },
        hovermode: 'closest',
        font: {
          family: 'Open Sans, sans-serif',
          color: 'black',
          size: 14
        },
        annotations: this.annotations
      };
    },

    traces() {

      let showDailyMarkers = this.filteredCovidData.length <= 2;

      // draws grey lines (line plot for each location)
      let trace1 = this.filteredCovidData.map((e, i) => ({
        x: e.cases.slice(0, this.day),
        y: e.slope.slice(0, this.day),
        name: e.country,
        text: this.dates.map(date => e.country + '<br>' + this.formatDate(date)),
        mode: showDailyMarkers ? 'lines+markers' : 'lines',
        type: 'scatter',
        legendgroup: i,
        marker: {
          size: 4,
          color: 'rgba(0,0,0,0.15)'
        },
        line: {
          color: 'rgba(0,0,0,0.15)'
        },
        hoverinfo: 'x+y+text',
        hovertemplate: '%{text}<br>Total ' + this.selectedData + ': %{x:,}<br>Weekly ' + this.selectedData + ': %{y:,}<extra></extra>',
      })
      );

      // draws red dots (most recent data for each location)
      let trace2 = this.filteredCovidData.map((e, i) => ({
        x: [e.cases[this.day - 1]],
        y: [e.slope[this.day - 1]],
        text: e.country,
        name: e.country,
        mode: this.showLabels ? 'markers+text' : 'markers',
        legendgroup: i,
        textposition: 'center right',
        marker: {
          size: 6,
          color: 'rgba(254, 52, 110, 1)'
        },
        hovertemplate: '%{data.text}<br>Total ' + this.selectedData + ': %{x:,}<br>Weekly ' + this.selectedData + ': %{y:,}<extra></extra>',

      }));

      if (this.showTrendLine && this.doublingTime > 0) {
        let cases = [1, 10000000];

        let trace3 = [{
          x: cases,
          y: cases.map(this.referenceLine),
          mode: 'lines',
          line: {
            dash: 'dot',
          },
          marker: {
            color: 'rgba(114, 27, 101, 0.7)'
          },
          hoverinfo: 'skip',
        }];

        // reference line must be last trace for annotation angle to work out
        return [...trace1, ...trace2, ...trace3];

      } else {
        return [...trace1, ...trace2];
      }

    },

    config() {
      return {
        responsive: true,
        toImageButtonOptions: {
          format: 'png', // one of png, svg, jpeg, webp
          filename: 'Covid Trends',
          height: 600,
          width: 600 * this.graphAttributes.width / this.graphAttributes.height,
          scale: 1 // Multiply title/legend/axis/canvas sizes by this factor
        }
      };
    },

    graphData() {
      return {
        uistate: { // graph is updated when uistate changes
          selectedData: this.selectedData,
          selectedRegion: this.selectedRegion,
          selectedScale: this.selectedScale,
          showLabels: this.showLabels,
          showTrendLine: this.showTrendLine,
          doublingTime: this.doublingTime,
        },
        traces: this.traces,
        layout: this.layout,
        config: this.config
      };
    },

    xmax() {
      return Math.max(...this.filteredCases, 50);
    },

    xmin() {
      return Math.min(...this.filteredCases, 50);
    },

    ymax() {
      return Math.max(...this.filteredSlope, 50);
    },

    ymin() {
      return Math.min(...this.filteredSlope);
    },

    filteredCases() {
      return Array.prototype.concat(...this.filteredCovidData.map(e => e.cases)).filter(e => !isNaN(e));
    },

    filteredSlope() {
      return Array.prototype.concat(...this.filteredCovidData.map(e => e.slope)).filter(e => !isNaN(e));
    },

    logxrange() {
      return [1, Math.ceil(Math.log10(1.5 * this.xmax))];
    },

    linearxrange() {
      return [-0.49 * Math.pow(10, Math.floor(Math.log10(this.xmax))), Math.round(1.2 * this.xmax)];
    },

    logyrange() {

      if (this.ymin < 10) { // shift ymin on log scale if fewer than 10 cases
        return [0, Math.ceil(Math.log10(1.5 * this.ymax))];
      } else {
        return [1, Math.ceil(Math.log10(1.5 * this.ymax))];
      }
    },

    linearyrange() {
      let ymax = Math.max(...this.filteredSlope, 50);
      return [-Math.pow(10, Math.floor(Math.log10(ymax)) - 2), Math.round(1.05 * ymax)];
    },

    xAnnotation() {

      if (this.selectedScale === 'Logarithmic Scale') {
        let x = this.logyrange[1] - Math.log10(this.referenceLine(1));
        if (x < this.logxrange[1]) {
          return x;
        } else {
          return this.logxrange[1];
        }

      } else {
        let x = this.linearyrange[1] / this.referenceLine(1);
        if (x < this.linearxrange[1]) {
          return x;
        } else {
          return this.linearxrange[1];
        }
      }
    },

    yAnnotation() {
      if (this.selectedScale === 'Logarithmic Scale') {
        let x = this.logyrange[1] - Math.log10(this.referenceLine(1));
        if (x < this.logxrange[1]) {
          return this.logyrange[1];
        } else {
          return this.logxrange[1] + Math.log10(this.referenceLine(1));
        }
      } else {
        let x = this.linearyrange[1] / this.referenceLine(1);
        if (x < this.linearxrange[1]) {
          return this.linearyrange[1];
        } else {
          return this.linearxrange[1] * this.referenceLine(1);
        }
      }

    }

  }
};
</script>

<style scoped>
  @import "normalize.css";
  @import "style.css";
</style>
